package com.ms.orm;

import com.ms.common.util.ReflectUtils;
import com.ms.common.util.StringUtils;
import com.ms.orm.annotation.*;
import com.ms.orm.context.DefaultLittleOrmContext;
import com.ms.orm.mapper.ClassPathScanningInterfaceCandidateComponentProvider;
import com.ms.orm.mapper.DynamicSqlRepository;
import com.ms.orm.mapper.GenericMapperFactoryBean;
import com.ms.orm.meta.EntityAndTableInfo;
import com.ms.orm.meta.JavaTypeMapperJdbcType;
import com.ms.orm.meta.PrimaryKey;
import com.ms.orm.meta.PropertyAndColumnInfo;
import com.ms.orm.util.TableNameFinder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import java.beans.PropertyDescriptor;
import java.beans.Transient;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * @author zdh
 * 用于扫描 被@TableName 注解注释的实体对象，并生成对象与数据关系映射源数据
 */
@Slf4j
public class EntityTableRelationRegistrar implements ImportBeanDefinitionRegistrar, ApplicationContextAware {

    /**
         是否已经加载，即是否加载了提供者配置文件
     */
    private final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
    private ApplicationContext applicationContext;
    private  BeanDefinitionRegistry registry;
    private  DynamicSqlRepository dynamicSqlRepository;

    @Autowired
    private  NamedParameterJdbcTemplate namedParameterJdbcTemplate;


    /**
     * 这里不注册bean定义，只是获取 注解中基础包路径，获取路径下@TableName注解类，生成关系映射数据
     * @param importingClassMetadata
     * @param registry
     */
    @SneakyThrows
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if(atomicBoolean.get()){
            return;
        }
        atomicBoolean.set(true);
        this.registry = registry;
        if(dynamicSqlRepository==null){
            dynamicSqlRepository =  new DynamicSqlRepository();
        }

        //getAnnotationAttributes();全限定名
       Map<String,Object> map =  importingClassMetadata.getAnnotationAttributes(EnableLittleOrm.class.getName());
       AnnotationAttributes attributes = AnnotationAttributes.fromMap(map);
       if(attributes==null){
           throw new IllegalArgumentException(String.format("@%s is not present on importing class '%s' as expected", EnableLittleOrm.class.getSimpleName(), importingClassMetadata.getClassName()));
       }

       try {
           //设置数据库类型，用于支持不同查询
           ReflectUtils.setStaticFieldValue(DefaultLittleOrmContext.class,"dbType",map.get("dbType"));
       }catch (Exception e){
           e.printStackTrace();
       }

        String[] basePackages = attributes.getStringArray("value");
       if(basePackages==null && basePackages.length==0){
           basePackages = attributes.getStringArray("basePackages");
       }
       //解析实体，封装元数据
        parseEntity(basePackages);

       //解析mapper,封装自定义sql操作数据库
        parseMapper(basePackages);

    }



    /**
     * 根据 实体bean定义，生成 实体源数据
     * @param annotationBeanDefinition
     */
    private static void createEntityMetaData(AnnotatedBeanDefinition annotationBeanDefinition){

        Map<String,Object> classAnnotationAttribute = annotationBeanDefinition.getMetadata().getAnnotationAttributes(TableName.class.getName());
        String tableName = (String) classAnnotationAttribute.get("value");
        // 获取类名
        String className = annotationBeanDefinition.getMetadata().getClassName();
        try {
            // 使用反射加载类
            Class<?> clazz = Class.forName(className);
            BeanWrapperImpl beanWrapper = new BeanWrapperImpl(clazz);


            EntityAndTableInfo tableInfo = new EntityAndTableInfo();
            if(tableName==null){
                tableName = TableNameFinder.convertToSnakeCaseTableName(clazz.getSimpleName());
            }
            tableInfo.setTableName(tableName);
            tableInfo.setEntityClass(clazz);
            tableInfo.setSimpleClassName(clazz.getSimpleName());

            List<PropertyAndColumnInfo> properties = new ArrayList<>();
            // 遍历所有字段
            for (Field field : clazz.getDeclaredFields()) {
                PropertyDescriptor descriptor = beanWrapper.getPropertyDescriptor(field.getName());
                //忽略瞬态值
                Transient aTransient = field.getAnnotation(Transient.class);
                if(aTransient!=null){
                    continue;
                }

                ID id = field.getAnnotation(ID.class);
                PrimaryKey primaryKey = null;
                if(id!=null){
                    primaryKey = new PrimaryKey();
                    primaryKey.setField(field);
                    primaryKey.setPropertyName(field.getName());
                    primaryKey.setColumnName(StringUtils.hasLength(id.value())?id.value() : StringUtils.propertyNameToColumnName(field.getName()));
                    primaryKey.setJavaType(field.getType());
                    primaryKey.setJdbcType(JavaTypeMapperJdbcType.getSqlType(field.getType()));
                    primaryKey.setIdType(id.type());
                    primaryKey.setPropertyWriterMethod(descriptor.getWriteMethod());
                    primaryKey.setPropertyReadMethod(descriptor.getReadMethod());

                    tableInfo.setPrimaryKey(primaryKey);
                }
                PropertyAndColumnInfo propertyInfo = new PropertyAndColumnInfo();
                //0、设置当前属性默认不是id
                propertyInfo.setIdExit(false);
                //1、设置 field
                propertyInfo.setField(field);
                //2、设置属性名
                propertyInfo.setPropertyName(field.getName());
                //3、设置是否存在
                propertyInfo.setFieldExit(true);
                //4、设置属性的Java类型
                propertyInfo.setJavaType(field.getType());
                //5、设置jdbc 数据类型
                propertyInfo.setJdbcType(JavaTypeMapperJdbcType.getSqlType(field.getType()));
                if(primaryKey!=null){
                    //6、设置数据库字段名，如果是id直接取id中的数据
                    propertyInfo.setColumnName(primaryKey.getColumnName());
                    propertyInfo.setIdExit(true);
                }else {
                    //如果不是id,判断是否有 TableField注解
                    TableField tableField = field.getAnnotation(TableField.class);
                    if (tableField != null) {
                        //读取属性对应的数据库字段
                        propertyInfo.setColumnName(StringUtils.hasLength(tableField.value())?tableField.value() : StringUtils.propertyNameToColumnName(field.getName()));
                        //使用注解中 exit 值覆盖默认值
                        propertyInfo.setFieldExit(tableField.exist());

                        propertyInfo.setInsertStrategy(tableField.insertStrategy());
                        propertyInfo.setUpdateStrategy(tableField.updateStrategy());
                        propertyInfo.setWhereStrategy(tableField.whereStrategy());
                    }else {
                        propertyInfo.setColumnName(StringUtils.propertyNameToColumnName(field.getName()));

                        //
                        propertyInfo.setInsertStrategy(FieldStrategy.DEFAULT);
                        propertyInfo.setUpdateStrategy(FieldStrategy.DEFAULT);
                        propertyInfo.setWhereStrategy(FieldStrategy.DEFAULT);
                    }
                }
                propertyInfo.setPropertyWriterMethod(descriptor.getWriteMethod());
                propertyInfo.setPropertyReadMethod(descriptor.getReadMethod());
                properties.add(propertyInfo);
            }
            tableInfo.setProperties(properties);
            tableInfo.setColumnMap(properties.stream().collect(Collectors.toMap(PropertyAndColumnInfo::getColumnName,p->p)));
            DefaultLittleOrmContext.getDefaultLittleOrmContext().addEntityAndTableInfo(tableInfo);

        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 解析实现，封装相关元数据操作
     * @param basePackages
     */
    private void parseEntity(String[] basePackages){
        // 创建一个默认的扫描器实例
        // false 表示不使用默认的过滤器
        ClassPathScanningCandidateComponentProvider scanner =  new ClassPathScanningCandidateComponentProvider(false);
        // 添加自定义过滤器，用于匹配特定注解
        scanner.addIncludeFilter(new AnnotationTypeFilter(TableName.class));

        Set<BeanDefinition> tableData  = new HashSet<>();
        // 执行扫描并返回结果
        for (int i = 0; i < basePackages.length; i++) {
            Set<BeanDefinition>  data = scanner.findCandidateComponents(basePackages[i]);
            if(data!=null && data.size()>0){
                tableData.addAll(data);
            }
        }
        if(tableData==null){
            throw new IllegalArgumentException("EntityTableRelationRegistrar cannot find tableEntity!!!");
        }else{
            for (BeanDefinition tableDatum : tableData) {
                if(tableDatum instanceof AnnotatedBeanDefinition){
                    AnnotatedBeanDefinition annotationBeanDefinition = (AnnotatedBeanDefinition)tableDatum;
                    createEntityMetaData(annotationBeanDefinition);
                }
            }
        }
    }

    /**
     * 解析 Mapper
     */
    private void parseMapper(String[] basePackages){
        // 创建一个默认的扫描器实例
        // false 表示不使用默认的过滤器
        ClassPathScanningInterfaceCandidateComponentProvider scanner =  new ClassPathScanningInterfaceCandidateComponentProvider();
        // 添加自定义过滤器，用于匹配特定注解
        scanner.addIncludeFilter(new AnnotationTypeFilter(LittleOrmRepository.class));

        Set<BeanDefinition> mapperInterface  = new HashSet<>();
        // 执行扫描并返回结果
        for (int i = 0; i < basePackages.length; i++) {
            Set<BeanDefinition>  data = scanner.findCandidateComponents(basePackages[i]);
            if(data!=null && data.size()>0){
                mapperInterface.addAll(data);
            }
        }
        if(mapperInterface==null){
            log.warn("EntityTableRelationRegistrar cannot find Mapper!!!");
        }else{
            for (BeanDefinition mapper : mapperInterface) {
                try {
                    Class<?> interfaceClass = Class.forName(mapper.getBeanClassName());
                    if (interfaceClass.isInterface()) {
                        registerFactoryBeanForInterface(interfaceClass);
                    }
                }catch (Exception e) {
                    log.error("registerFactoryBeanForInterface Exception",e);
                }
            }
        }
    }

    private void registerFactoryBeanForInterface(Class interfaceClass) {

        RootBeanDefinition factoryBeanDef = new RootBeanDefinition(GenericMapperFactoryBean.class);
        factoryBeanDef.getConstructorArgumentValues().addGenericArgumentValue(interfaceClass);
        factoryBeanDef.getConstructorArgumentValues().addGenericArgumentValue(dynamicSqlRepository);
        factoryBeanDef.getConstructorArgumentValues().addGenericArgumentValue(namedParameterJdbcTemplate);
        //String beanName = interfaceClass.getSimpleName() + "Factory";
        String beanName = interfaceClass.getSimpleName();
        beanName = StringUtils.uncapitalize(beanName);
        registry.registerBeanDefinition(beanName, factoryBeanDef);
    }
}
